package com.google.code.sms.controller;

import com.google.code.sms.domain.CoverArtScheme;
import com.google.code.sms.domain.MediaElement;
import com.google.code.sms.domain.Player;
import com.google.code.sms.service.MediaElementService;
import com.google.code.sms.service.PlayerService;
import com.google.code.sms.service.SecurityService;
import com.google.code.sms.service.SettingsService;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Controller for the main page.
 *
 * @author Scott Ware
 */
public class MainController extends ParameterizableViewController {

    private SecurityService securityService;
    private PlayerService playerService;
    private SettingsService settingsService;
    private MediaElementService mediaElementService;

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        Map<String, Object> map = new HashMap<>();

        Player player = playerService.getPlayer(request, response);
        List<MediaElement> mediaElements = getMediaElements(request);

        if (mediaElements.isEmpty())
        {
            return new ModelAndView(new RedirectView("notFound.view"));
        }

        // Get the selected directory
        MediaElement directory = mediaElements.get(0);
        if (!directory.isDirectory())
        {
            directory = mediaElementService.getParentOf(directory);
        }

        // Redirect if the directory cannot be retrieved
        if (directory == null)
        {
            return new ModelAndView(new RedirectView("home.view?"));
        }

        // Get the child elements for the given directory element
        List<MediaElement> children = new ArrayList<>();
        
        for (MediaElement mediaElement : mediaElements) 
        {
            if (mediaElement != null && !mediaElement.isDirectory())
            {
                mediaElement = mediaElementService.getParentOf(mediaElement);
            }
            
            if(mediaElement != null)
            {
                children.addAll(mediaElementService.getChildrenOf(mediaElement.getPath(), true, true, true, true));
            }
        }
        
        // Collate information for Main windows
        map.put("directory", directory);
        map.put("ancestors", getAncestors(directory));
        map.put("children", children);
        map.put("player", player);
        map.put("user", securityService.getCurrentUser(request));
        map.put("updateNowPlaying", request.getParameter("updateNowPlaying") != null);

        MediaElement parent = mediaElementService.getParentOf(directory);
        map.put("parent", parent);
        map.put("navigateUpAllowed", parent != null);

        CoverArtScheme scheme = player.getCoverArtScheme();
        if (scheme != CoverArtScheme.OFF)
        {
            List<MediaElement> coverArts = getCoverArts(directory, children);
            int size = scheme.getSize();
            if(directory.isDirectory() && coverArts.size() == 1)
            {
                size *= 2;
            }
            map.put("coverArts", coverArts);
            map.put("coverArtSize", size);
            if (coverArts.isEmpty())
            {
                map.put("showGenericCoverArt", true);
            }
        }

        setPreviousAndNextDirectory(directory, map);

        ModelAndView result = super.handleRequestInternal(request, response);
        result.addObject("model", map);
        return result;
    }

    /**
     * Get requested Media Elements.
     * 
     * @param request ServletRequest containing Media Element information.
     * @return Requested Media Elements
     */
    private List<MediaElement> getMediaElements(HttpServletRequest request)
    {
        List<MediaElement> mediaElements = new ArrayList<>();
        
        for (String path : ServletRequestUtils.getStringParameters(request, "path"))
        {
            MediaElement mediaElement = mediaElementService.getMediaElement(path);
            
            if (mediaElement != null)
            {
                mediaElements.add(mediaElement);
            }
        }
        
        for (int id : ServletRequestUtils.getIntParameters(request, "id"))
        {
            MediaElement mediaElement = mediaElementService.getMediaElement(id);
            
            if (mediaElement != null)
            {
                mediaElements.add(mediaElement);
            }
        }
        
        return mediaElements;
    }

    /**
     * Retrieves cover art for all Media Elements in current request.
     * 
     * @param directory Directory Media Element to scan for cover art.
     * @param children Child Media Elements to scan for cover art.
     * @return Media Elements with associated cover art.
     * @throws IOException 
     */
    private List<MediaElement> getCoverArts(MediaElement directory, List<MediaElement> children) throws IOException
    {
        List<MediaElement> coverArts = new ArrayList<>();
        
        if (directory.getMetaData().getCoverArtPath() != null)
        {
            coverArts.add(directory);
        } 
        else
        {
            for (MediaElement child : children)
            {
                if (child.getMetaData().getCoverArtPath() != null)
                {
                    coverArts.add(child);
                }
            }
        }
        
        return coverArts;
    }

    /**
     * Returns the ancestor directory elements for a given Media Element.
     * 
     * @param directory A directory Media Element
     * @return The Media Elements which relate to ancestor directories of the given Media Element.
     * @throws IOException 
     */
    private List<MediaElement> getAncestors(MediaElement directory) throws IOException
    {
        LinkedList<MediaElement> result = new LinkedList<>();

        MediaElement parent = mediaElementService.getParentOf(directory);
        
        while (parent != null)
        {
            result.addFirst(parent);
            parent = mediaElementService.getParentOf(parent);
        }
            
        return result;
    }

    /**
     * Determines the previous and next directory elements for a given Media Element
     * 
     * @param directory Current Media Element.
     * @param map Object Map.
     * @throws IOException 
     */
    private void setPreviousAndNextDirectory(MediaElement directory, Map<String, Object> map) throws IOException
    {
        // Get parent directory for given Media Element
        MediaElement parent = mediaElementService.getParentOf(directory);

        if(parent != null)
        {
            // Get al directory elements within parent directory
            List<MediaElement> children = mediaElementService.getChildrenOf(parent.getPath(), false, false, true, true);

            // Establish if other directories exist before and after current directory
            int index = children.indexOf(directory);
            if (index > 0)
            {
                map.put("previousDirectory", children.get(index - 1));
            }
            if (index < children.size() - 1)
            {
                map.put("nextDirectory", children.get(index + 1));
            }
        }
    }

    //
    // Services
    //
    
    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    public void setPlayerService(PlayerService playerService) {
        this.playerService = playerService;
    }

    public void setSettingsService(SettingsService settingsService) {
        this.settingsService = settingsService;
    }

    public void setMediaElementService(MediaElementService mediaElementService) {
        this.mediaElementService = mediaElementService;
    }
}